/**
 * 
 */
package il.ac.bgu.cs.heuristics.graph;

import il.ac.bgu.cs.heuristics.graph.State.Builder;
import il.ac.bgu.cs.heuristics.graph.types.IAction;
import il.ac.bgu.cs.heuristics.graph.types.IPath;
import il.ac.bgu.cs.heuristics.graph.types.IState;
import il.ac.bgu.cs.heuristics.graph.types.ISubstitution;
import il.ac.bgu.cs.heuristics.utils.CollectionUtils;

import java.util.Collections;
import java.util.List;
import java.util.Set;

/**
 * @author Ronen Pinko
 * 
 */
public class Path implements IPath {

	private final List<IAction>		actions				= CollectionUtils
																.newArrayList();
	private final int				weight;
	private final List<ActionEdge>	pathEdges;
	private Set<ISubstitution>		constraintList;
	private IState					extendedGoalState;
	private boolean					contradictingPath	= false;

	public boolean isContradictingPath() {
		return contradictingPath;
	}

	public Path(List<ActionEdge> edges) {
		this.pathEdges = edges;
		int w = 0;
		for (ActionEdge edge : edges) {
			if (!edge.isNonContradicting())
				actions.add(edge.getAction());
			w += edge.getWeight();
		}
		weight = w;
		getConstraints();
	}

	@Override
	public List<IAction> getActions() {
		return Collections.unmodifiableList(actions);
	}

	public Set<ISubstitution> getConstraintsOldVersion() {
		if (constraintList == null) {
			constraintList = CollectionUtils.newLinkedHashSet();
			for (ActionEdge edge : pathEdges) {
				if (edge.isNonContradicting()) {
					constraintList.addAll(extractEdgeConstraints(edge));
				}
			}
		}
		return Collections.unmodifiableSet(constraintList);
	}

	private Set<ISubstitution> extractEdgeConstraints(ActionEdge edge) {
		final Set<ISubstitution> edgeConstraints = CollectionUtils
				.newLinkedHashSet();
		final IState from = edge.getFrom();
		final IState to = edge.getTo();
		for (ISubstitution toSub : to.getSubstitutions()) {
			if (!stateContainsSubstitution(from, toSub)) {
				edgeConstraints.add(toSub);
			}
		}
		return Collections.unmodifiableSet(edgeConstraints);
	}

	@Override
	public Set<ISubstitution> getConstraints() {
		if (constraintList == null) {
			constraintList = CollectionUtils.newLinkedHashSet();
			Set<ISubstitution> effects = CollectionUtils.newLinkedHashSet();
			IState effectsState = State.createState(effects);
			for (ActionEdge edge : pathEdges) {
				if (!edge.isNonContradicting()) {
					addEdgeToPreconditionAndEffectSets(constraintList,
							effectsState, edge);
				}
			}
		}

		return Collections.unmodifiableSet(constraintList);
	}

	private boolean addEdgeToPreconditionAndEffectSets(
			Set<ISubstitution> preconditions, IState effectsState,
			ActionEdge edge) {

		IState precondition = edge.getFrom();
		for (ISubstitution preconditionSub : precondition.getSubstitutions()) {
			if (isSubstitutionSetNegateSub(effectsState.getSubstitutions(),
					preconditionSub)) {
				this.contradictingPath = true;
				continue;
			}
			if (effectsState.contains(preconditionSub)) {
				continue;
			}
			if (isSubstitutionSetNegateSub(preconditions, preconditionSub)) {
				this.contradictingPath = true;
				continue;
			}
			preconditions.add(preconditionSub);
		}
		IState effect = edge.getTo();
		effectsState.swap(effect.mergeAndOverride(effectsState));
		return false;
	}

	private static boolean isSetNegateSet(Set<ISubstitution> firstSet,
			Set<ISubstitution> SecondSet) {

		for (ISubstitution sub : firstSet) {
			if (isSubstitutionSetNegateSub(SecondSet, sub))
				return true;
		}
		return false;
	}

	private static boolean isSubstitutionSetNegateSub(
			Set<ISubstitution> subsSet, ISubstitution sub) {
		for (ISubstitution subOfSet : subsSet) {
			if (subOfSet.isNegateSubstitution(sub))
				return true;
		}
		return false;
	}

	private boolean stateContainsSubstitution(final IState state,
			ISubstitution substitution) {
		for (ISubstitution other : state.getSubstitutions()) {
			if (substitution.equals(other)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public int weight() {
		return weight;
	}

	@Override
	public String toString() {
		StringBuffer sb = new StringBuffer();
		sb.append("Path is: \n");
		for (int i = 0; i < pathEdges.size(); i++) {
			if (i == 0) {
				sb.append("Init node: ");
				sb.append(pathEdges.get(i).getFrom().toString());
				sb.append("\n");
				continue;
			}
			sb.append(pathEdges.get(i).getFrom().toString());
			sb.append(" -> ");
			sb.append(pathEdges.get(i).getTo().toString());
		}
		return sb.toString();
	}

	public IState getExtendedGoalState() {

		if (this.extendedGoalState != null)
			return this.extendedGoalState;
		State.Builder builder = new Builder();
		IState state = new State(builder);
		for (ActionEdge edge : pathEdges) {
			state = edge.getFrom().mergeAndOverride(state);
			state = edge.getTo().mergeAndOverride(state);

		}
		return state;
	}

}
